use scopeguard::defer;

use bc_base::{
    BlockHeight, BlockId, PublicKeyHashBytes,
    constants::{ALICE_PUBLIC_KEY_HASH, BOB_PUBLIC_KEY_HASH, GENESIS_BLOCK_ID},
};
use bc_lib::mining::MiningCommand;

use tests::common;

#[tokio::test]
async fn local_mining() {
    let (mut test_env, tmp_dir) = common::setup("local_mining", true, None, false).await;
    defer! {
        tmp_dir.close().unwrap();
    };

    // do test here
    {
        common::local::add_alice_keypair_to_wallet(test_env.bc_manager.clone()).await;

        // create block
        let prev_block_hash = BlockId::from_hex_str(GENESIS_BLOCK_ID).unwrap();
        let block_height: BlockHeight = 1;
        let miner_pubkey_hash = PublicKeyHashBytes::from_hex_str(ALICE_PUBLIC_KEY_HASH).unwrap();
        let _block_id = common::local::create_block_only_contain_coinbase_and_add_to_blockchain(
            test_env.bc_manager.clone(),
            &prev_block_hash,
            block_height,
            &miner_pubkey_hash,
        )
        .await;

        let bc_manager = test_env.bc_manager.lock().await;
        assert_eq!(bc_manager.block_number().await, 2);
        assert_eq!(bc_manager.transaction_number().await, 2);

        // create tx
        let payer_pubkey_hash = PublicKeyHashBytes::from_hex_str(ALICE_PUBLIC_KEY_HASH).unwrap();
        let payee_pubkey_hash = PublicKeyHashBytes::from_hex_str(BOB_PUBLIC_KEY_HASH).unwrap();
        let tx = bc_manager
            .create_transaction(&payer_pubkey_hash, &payee_pubkey_hash, 100)
            .await
            .unwrap();
        let tx_serialize = tx.serialize();
        let tx_id = tx.calc_tx_id();
        bc_manager
            .process_transaction(tx_id, tx.clone(), tx_serialize)
            .await
            .unwrap();

        assert_eq!(bc_manager.block_number().await, 2);
        assert_eq!(bc_manager.transaction_number().await, 2);
        drop(bc_manager);

        // MiningCommand::Start(payer_pubkey_hash)
        test_env
            .bc_manager
            .lock()
            .await
            .send_cmd_to_ming_task(MiningCommand::Start(payer_pubkey_hash))
            .await;

        // MiningCommand::GenBlockWhenReceiveTx(true)
        test_env
            .bc_manager
            .lock()
            .await
            .send_cmd_to_ming_task(MiningCommand::GenBlockWhenReceiveTx(true))
            .await;

        tokio::time::sleep(std::time::Duration::from_millis(10)).await; // wait mining task to start.

        // send tx to mine task.
        test_env
            .bc_manager
            .lock()
            .await
            .send_tx_to_mine_task(tx)
            .await;
        tracing::debug!("Had send tx to mine task.");

        // wait tx to be mined.
        let mut wait_cnt = 0u32;
        loop {
            tracing::debug!(">>>>>>>> waiting for miner to calculate block hash.");

            let bc_manager = test_env.bc_manager.lock().await;
            if !bc_manager.get_gen_block_when_receive_tx_flag() {
                break;
            }
            drop(bc_manager);

            tokio::time::sleep(std::time::Duration::from_millis(300)).await;
            wait_cnt += 1;
            tracing::debug!(">>>>>>> waiting cnt: {}", wait_cnt);

            if wait_cnt > 6 {
                tracing::error!("waiting for miner to calculate block hash timeout.");
                break;
            }
        }

        // shutdown task_mine
        common::shutdown_task_mine(&mut test_env).await;

        tracing::debug!("gen a block.");

        assert_eq!(test_env.bc_manager.lock().await.block_number().await, 3);
        assert_eq!(
            test_env.bc_manager.lock().await.transaction_number().await,
            3 + 1
        );

        tracing::debug!("check over.")
    }

    tracing::debug!("Testing local mining end.");

    common::teardown(test_env).await;
}
